﻿using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Cache;
using UtilZ.Dotnet.Ex.Communication.Base;
using UtilZ.Dotnet.Ex.Communication.Net.Base;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Communication.Net.Udp
{

    /**********************************************************************
     * 简单网通通信协议
     * 
     * 字段       数据类型    字节大小    值范围
     * 同步字      uint32     4           0xF99FEFFE
     * 版本号      uint16     2           1 
     * 会话标识    uint16     2           0-65535
     * 数据类型    uint16     2           1:发送数据;2:数据确认;3:心跳
     * 数据长度    uint16     2           包含包头
     * 数据        byte[]     数据长度    byte数组
     **********************************************************************/

    /// <summary>
    /// 基于udp的简单网络通道,通过重试机制保证数据可靠性送达目的地
    /// </summary>
    unsafe public class SNTChannel : UnpackerAbs, IDisposable
    {
        /// <summary>
        /// [接收方数据去重,发送方事件通知对象]缓存
        /// </summary>
        private readonly static ObjectCache _cache = null;

        static SNTChannel()
        {
            _cache = new ObjectCache();
        }



        private const int _HEAD_SIZE = 12;
        private const uint _HEAD_SYNC = 0xF99FEFFE;
        private const short _VER = 1;

        private const ushort _DATA = 1;
        private const ushort _ACK = 2;
        private const ushort _REQUEST = 3;
        private const ushort _REQUEST_REPLY = 4;

        private readonly int _sendMillisecondsTimeout;
        private readonly int _timeoutRepeatCount;
        private readonly int _cacheExpireMilliseconds;

        private readonly NetUdpClient _udpClient;
        private ushort _sidIndex = 1;
        private byte[] _buffer = null;
        private BinaryWriter _writer = null;
        private readonly object _sendLock = new object();

        private readonly byte[] _ackBuffer;
        private readonly BinaryWriter _ackWriter;




        /// <summary>
        /// 构造函数初始化
        /// </summary>
        /// <param name="initPara">udp初始化参数</param>
        /// <param name="ackMillisecondsTimeout">发送数据超时等待确认超时时长,单位:毫秒</param>
        /// <param name="timeoutRepeatCount">等待确认超时后重试次数</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public SNTChannel(NetUdpClientInitPara initPara, int ackMillisecondsTimeout = 1000, int timeoutRepeatCount = 3)
        {
            if (initPara == null)
            {
                throw new ArgumentNullException(nameof(initPara));
            }

            if (ackMillisecondsTimeout < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(ackMillisecondsTimeout), "发送数据超时等待确认超时时长无效,必须大于0");
            }

            initPara.SetUnpacker(this);
            this._udpClient = new NetUdpClient(initPara);
            this._sendMillisecondsTimeout = ackMillisecondsTimeout;
            this._timeoutRepeatCount = timeoutRepeatCount;

            if (timeoutRepeatCount > 0)
            {
                this._cacheExpireMilliseconds = ackMillisecondsTimeout * timeoutRepeatCount;
            }
            else
            {
                this._cacheExpireMilliseconds = ackMillisecondsTimeout;
            }
            this._cacheExpireMilliseconds += 100;

            this._buffer = new byte[initPara.SendBufferSize];
            this._writer = new BinaryWriter(new MemoryStream(this._buffer));

            this._ackBuffer = new byte[_HEAD_SIZE];
            this._ackWriter = new BinaryWriter(new MemoryStream(this._ackBuffer));
            this.PrimitiveWriteHead(this._ackWriter, 0, _ACK, (ushort)0);
            this._udpClient.Start();
        }



        #region SendTo

        private ushort WriteHead(int dataLength, ushort dataType)
        {
            int length = _HEAD_SIZE + dataLength;
            if (length > NetHelper.UDP_PACKAGE_MAX)
            {
                throw new ArgumentException($"数据长度\"{dataLength}\"超出udp协议支持的最大长度\"{NetHelper.UDP_PACKAGE_MAX - _HEAD_SIZE}\"");
            }

            if (this._buffer != null && this._buffer.Length < length)
            {
                this._writer.Dispose();

                this._buffer = null;
                this._writer = null;
            }

            if (this._buffer == null)
            {
                this._buffer = new byte[length];
                this._writer = new BinaryWriter(new MemoryStream(this._buffer));
            }

            this._writer.BaseStream.Position = 0L;
            ushort sid = this._sidIndex++;
            this.PrimitiveWriteHead(this._writer, sid, dataType, (ushort)length);
            return sid;
        }

        private void PrimitiveWriteHead(BinaryWriter writer, ushort sid, ushort dataType, ushort dataLength)
        {
            writer.Write(_HEAD_SYNC);//同步字
            writer.Write(_VER);//版本号
            writer.Write(sid);//会话标识
            writer.Write(dataType);//数据类型
            writer.Write(dataLength);//数据长度
        }

        private void PrimitveSendTo(ushort sid, EndPoint dstEndPoint)
        {
            int count = 0;
            string key = this.GenerateEventWaitHandleCacheKey(sid);
            using (var eventHandler = new AutoResetEvent(false))
            {
                _cache.Add(key, eventHandler, this._cacheExpireMilliseconds);

                while (true)
                {
                    this._udpClient.SendTo(this._buffer, 0, (int)this._writer.BaseStream.Position, System.Net.Sockets.SocketFlags.None, dstEndPoint);
                    if (eventHandler.WaitOne(this._sendMillisecondsTimeout))
                    {
                        _cache.Remove(key);
                        break;
                    }

                    if (count < this._timeoutRepeatCount)
                    {
                        count++;
                        continue;
                    }
                    else
                    {
                        _cache.Remove(key);
                        throw new TimeoutException($"发送数据到\"{NetHelper.GetIPPortString(dstEndPoint)}\"后等待数据超时,重试\"{count}\"次后依然超时,发送失败.");
                    }
                }
            }
        }

        private string GenerateEventWaitHandleCacheKey(ushort sid)
        {
            return $"{this.GetHashCode()}_{sid}";
        }

        private string GenerateRequestEventWaitHandleCacheKey(ushort sid)
        {
            return $"{this.GetHashCode()}_req_{sid}";
        }


#if NET4_0
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        public void SendTo(byte[] buffer, EndPoint dstEndPoint)
        {
            lock (this._sendLock)
            {
                ushort sid = this.WriteHead(buffer.Length, _DATA);
                this._writer.Write(buffer);
                this.PrimitveSendTo(sid, dstEndPoint);
            }
        }
#else
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        public void SendTo(Span<byte> buffer, EndPoint dstEndPoint)
        {
            lock (this._sendLock)
            {
                ushort sid = this.WriteHead(buffer.Length, _DATA);
                this._writer.Write(buffer);
                this.PrimitveSendTo(sid, dstEndPoint);
            }
        }
#endif

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        /// <param name="offset">数据起始位置</param>
        /// <param name="length">数据长度</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        public void SendTo(byte[] buffer, int offset, int length, EndPoint dstEndPoint)
        {
            lock (this._sendLock)
            {
                ushort sid = this.WriteHead(buffer.Length, _DATA);
                this._writer.Write(buffer, offset, length);
                this.PrimitveSendTo(sid, dstEndPoint);
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        /// <exception cref="NotSupportedException"></exception>
        public void SendTo(DataSpan<byte> buffer, EndPoint dstEndPoint)
        {
            if (buffer == null)
            {
                return;
            }

            lock (this._sendLock)
            {
                ushort sid = this.WriteHead(buffer.Length, _DATA);
                switch (buffer.DataType)
                {
                    case DataSpanDataType.DataPtr:
                        this._writer.Write(new Span<byte>(buffer.DataPtr, buffer.Length));
                        break;
                    case DataSpanDataType.Array:
                        this._writer.Write(buffer.Array, buffer.Offset, buffer.Length);
                        break;
                    default:
                        throw new NotSupportedException($"不支持的数据类型\"{buffer.DataType.ToString()}\"");
                }

                this.PrimitveSendTo(sid, dstEndPoint);
            }
        }

        #endregion



        #region Request
        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<RequestDataOutputArgs> RequestOutput;

        private void WriteRequestHead(int dataLength, ushort sid)
        {
            int length = _HEAD_SIZE + dataLength;
            if (length > NetHelper.UDP_PACKAGE_MAX)
            {
                throw new ArgumentException($"数据长度\"{dataLength}\"超出udp协议支持的最大长度\"{NetHelper.UDP_PACKAGE_MAX - _HEAD_SIZE}\"");
            }

            if (this._buffer != null && this._buffer.Length < length)
            {
                this._writer.Dispose();

                this._buffer = null;
                this._writer = null;
            }

            if (this._buffer == null)
            {
                this._buffer = new byte[length];
                this._writer = new BinaryWriter(new MemoryStream(this._buffer));
            }

            this._writer.BaseStream.Position = 0L;
            this.PrimitiveWriteHead(this._writer, sid, _REQUEST_REPLY, (ushort)length);
        }

        private byte[] PrimitveRequest(ushort sid, EndPoint dstEndPoint, int millisecondsTimeout)
        {
            string key = this.GenerateRequestEventWaitHandleCacheKey(sid);
            using (var eventWaitHandleInfo = new EventWaitHandleInfo(key, new AutoResetEvent(false)))
            {
                _cache.Add(key, eventWaitHandleInfo, millisecondsTimeout * 2);
                this.PrimitveSendTo(sid, dstEndPoint);

                if (eventWaitHandleInfo.WaitOne(millisecondsTimeout))
                {
                    _cache.Remove(key);
                    return (byte[])eventWaitHandleInfo.Tag;
                }
                else
                {
                    _cache.Remove(key);
                    throw new TimeoutException($"到\"{NetHelper.GetIPPortString(dstEndPoint)}\"请求数据超时.");
                }
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="str">数据</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        /// <param name="millisecondsTimeout">请求等待响应超时时长,单位:毫秒</param>
        /// <returns>请求结果</returns>
        public string Request(string str, EndPoint dstEndPoint, int millisecondsTimeout = 1000)
        {
            if (str == null)
            {
                throw new ArgumentNullException(nameof(str));
            }

            byte[] buffer = Encoding.UTF8.GetBytes(str);

            lock (this._sendLock)
            {
                ushort sid = this.WriteHead(buffer.Length, _REQUEST);
                this._writer.Write(buffer);
                byte[] data = this.PrimitveRequest(sid, dstEndPoint, millisecondsTimeout);
                return Encoding.UTF8.GetString(data);
            }
        }

#if NET4_0
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        /// <param name="millisecondsTimeout">请求等待响应超时时长,单位:毫秒</param>
        /// <returns>请求结果</returns>
        public byte[] Request(byte[] buffer, EndPoint dstEndPoint, int millisecondsTimeout = 1000)
        {
            lock (this._sendLock)
            {
                ushort sid = this.WriteHead(buffer.Length, _REQUEST);
                this._writer.Write(buffer);
                return this.PrimitveRequest(sid, dstEndPoint, millisecondsTimeout);
            }
        }
#else
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        /// <param name="millisecondsTimeout">请求等待响应超时时长,单位:毫秒</param>
        /// <returns>请求结果</returns>
        public byte[] Request(Span<byte> buffer, EndPoint dstEndPoint, int millisecondsTimeout = 1000)
        {
            lock (this._sendLock)
            {
                ushort sid = this.WriteHead(buffer.Length, _REQUEST);
                this._writer.Write(buffer);
                return this.PrimitveRequest(sid, dstEndPoint, millisecondsTimeout);
            }
        }
#endif

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        /// <param name="offset">数据起始位置</param>
        /// <param name="length">数据长度</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        /// <param name="millisecondsTimeout">请求等待响应超时时长,单位:毫秒</param>
        /// <returns>请求结果</returns>
        public byte[] Request(byte[] buffer, int offset, int length, EndPoint dstEndPoint, int millisecondsTimeout = 1000)
        {
            lock (this._sendLock)
            {
                ushort sid = this.WriteHead(buffer.Length, _REQUEST);
                this._writer.Write(buffer, offset, length);
                return this.PrimitveRequest(sid, dstEndPoint, millisecondsTimeout);
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        /// <param name="millisecondsTimeout">请求等待响应超时时长,单位:毫秒</param>
        /// <returns>请求结果</returns>
        /// <exception cref="NotSupportedException"></exception>
        public byte[] Request(DataSpan<byte> buffer, EndPoint dstEndPoint, int millisecondsTimeout = 1000)
        {
            if (buffer == null)
            {
                return null;
            }

            lock (this._sendLock)
            {
                ushort sid = this.WriteHead(buffer.Length, _REQUEST);
                switch (buffer.DataType)
                {
                    case DataSpanDataType.DataPtr:
                        this._writer.Write(new Span<byte>(buffer.DataPtr, buffer.Length));
                        break;
                    case DataSpanDataType.Array:
                        this._writer.Write(buffer.Array, buffer.Offset, buffer.Length);
                        break;
                    default:
                        throw new NotSupportedException($"不支持的数据类型\"{buffer.DataType.ToString()}\"");
                }

                return this.PrimitveRequest(sid, dstEndPoint, millisecondsTimeout);
            }
        }

        #endregion



        /// <summary>
        /// 
        /// </summary>
        /// <param name="unpackerPara"></param>
        /// <returns></returns>
        protected override int PrimitiveUnPackge(IUnpackerPara unpackerPara)
        {
            this.UnPackge((NetUdpUnpackerPara)unpackerPara);
            return unpackerPara.DataLength;
        }


        private DataSource _dataSource = null;
        private readonly DataOutputArgs _dataOutputArgs = new DataOutputArgs();
        private void UnPackge(NetUdpUnpackerPara udpUnpackerPara)
        {
            if (udpUnpackerPara.DataLength < _HEAD_SIZE)
            {
                ZLoger.Warn($"简单udp通信收到无效数据,数据:{ConvertEx.BytesToHexadecimaString(udpUnpackerPara.Buffer, udpUnpackerPara.Offset, udpUnpackerPara.DataLength)}");
                return;
            }

            var reader = udpUnpackerPara.Reader;
            var linkInfo = (NetLinkInfo)udpUnpackerPara.LinkInfo;
            var udpclient = (NetUdpClient)udpUnpackerPara.Client;

            var sync = reader.ReadUInt32();//同步字
            if (sync != _HEAD_SYNC)
            {
                ZLoger.Warn($"简单udp通信收到无效数据,数据:{ConvertEx.BytesToHexadecimaString(udpUnpackerPara.Buffer, udpUnpackerPara.Offset, udpUnpackerPara.DataLength)}");
                return;
            }

            var ver = reader.ReadUInt16();//版本号
            if (ver != _VER)
            {
                ZLoger.Warn($"简单udp通信收到无效数据,其版本号\"{ver}\"与期望版本号\"{_VER}\"不匹配,数据:{ConvertEx.BytesToHexadecimaString(udpUnpackerPara.Buffer, udpUnpackerPara.Offset, udpUnpackerPara.DataLength)}");
                return;
            }

            ushort sid = reader.ReadUInt16();//会话标识
            ushort dataType = reader.ReadUInt16();//数据类型
            int pkLength = reader.ReadUInt16();//数据长度
            int length = pkLength - _HEAD_SIZE;

            if (dataType == _DATA)
            {
                //回复确认消息
                this.SendAck(linkInfo, udpclient, sid);

                string key = $"{NetHelper.GetIPPortString(linkInfo.RemoteEndPoint)}_{sid}";
                if (!_cache.Exist(key))
                {
                    if (length > 0)
                    {
                        if (this._dataSource == null || this._dataSource.Bytes != udpUnpackerPara.Buffer)
                        {
                            this._dataSource = new DataSource(udpUnpackerPara.Buffer, (int)reader.BaseStream.Position, length);
                        }
                        else
                        {
                            this._dataSource.Update((int)reader.BaseStream.Position, length);
                        }
                    }
                    else
                    {
                        this._dataSource.Update(0, 0);
                    }

                    //输出数据
                    this._dataOutputArgs.Update(linkInfo, this._dataSource);
                    base.OnRaiseOnReceivedData(udpclient, this._dataOutputArgs);
                    _cache.Set(key, null, this._cacheExpireMilliseconds);
                }


            }
            else if (dataType == _REQUEST)
            {
                //回复确认消息
                this.SendAck(linkInfo, udpclient, sid);

                string key = $"req_ret_{NetHelper.GetIPPortString(linkInfo.RemoteEndPoint)}_{sid}";
                RequestDataOutputArgs requestDataOutputArgs = _cache.Get(key) as RequestDataOutputArgs;
                if (requestDataOutputArgs == null)
                {
                    //请求
                    var handler = this.RequestOutput;
                    if (handler != null)
                    {
                        requestDataOutputArgs = new RequestDataOutputArgs(linkInfo, reader.ReadBytes(length));
                        Task.Factory.StartNew(() =>
                        {
                            try
                            {
                                handler(this, requestDataOutputArgs);
                                _cache.Set(key, requestDataOutputArgs, this._cacheExpireMilliseconds);
                                this.SendRequestReply(linkInfo, sid, requestDataOutputArgs);
                            }
                            catch (Exception ex)
                            {
                                ZLoger.Error(ex);
                            }
                        });
                    }
                    else
                    {
                        ZLoger.Warn($"收到请求,但未注册请求处理事件");
                    }
                }
                else
                {
                    this.SendRequestReply(linkInfo, sid, requestDataOutputArgs);
                }
            }
            else if (dataType == _ACK)
            {
                //发送收到数据确认通知
                string key = this.GenerateEventWaitHandleCacheKey(sid);
                var eventHandler = _cache.Get(key) as AutoResetEvent;
                if (eventHandler != null)
                {
                    try
                    {
                        eventHandler.Set();
                    }
                    catch (ObjectDisposedException)
                    {

                    }
                }
            }
            else if (dataType == _REQUEST_REPLY)
            {
                //回复确认消息
                this.SendAck(linkInfo, udpclient, sid);

                string key = this.GenerateRequestEventWaitHandleCacheKey(sid);
                var eventWaitHandleInfo = _cache.Get(key) as EventWaitHandleInfo;
                if (eventWaitHandleInfo != null)
                {
                    try
                    {
                        eventWaitHandleInfo.Set(reader.ReadBytes(length));
                    }
                    catch (ObjectDisposedException)
                    {

                    }
                }
            }

        }

        private void SendRequestReply(NetLinkInfo linkInfo, ushort sid, RequestDataOutputArgs requestDataOutputArgs)
        {
            lock (this._sendLock)
            {
                this.WriteRequestHead(requestDataOutputArgs.Result.Length, sid);
                this._writer.Write(requestDataOutputArgs.Result);
                this.PrimitveSendTo(sid, linkInfo.RemoteEndPoint);
            }
        }

        private void SendAck(NetLinkInfo linkInfo, NetUdpClient udpclient, ushort sid)
        {
            lock (this._ackWriter)
            {
                this._ackWriter.BaseStream.Position = 6;
                this._ackWriter.Write(sid);
                udpclient.SendTo(this._ackBuffer, linkInfo.RemoteEndPoint);
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            this._udpClient.Dispose();
            if (this._writer != null)
            {
                this._writer.Dispose();
                this._writer = null;
            }
            this._ackWriter.Dispose();
        }

    }

    /// <summary>
    /// 接收数据输出事件参数类
    /// </summary>
    public class RequestDataOutputArgs : EventArgs
    {
        private LinkInfoAbs _linkInfo = null;
        /// <summary>
        /// 连接信息
        /// </summary>
        public LinkInfoAbs LinkInfo
        {
            get { return _linkInfo; }
        }

        private readonly byte[] _data = null;
        /// <summary>
        /// 输出的数据
        /// </summary>
        public byte[] Data
        {
            get { return _data; }
        }

        private readonly DataSpan<byte> _result = new DataSpan<byte>();
        /// <summary>
        /// 结果数据
        /// </summary>
        public DataSpan<byte> Result
        {
            get { return this._result; }
        }


        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="linkInfo">连接信息</param>
        /// <param name="data">数据</param>
        public RequestDataOutputArgs(LinkInfoAbs linkInfo, byte[] data)
        {
            this._linkInfo = linkInfo;
            this._data = data;
        }

    }
}
